/**
 ****************************************************************************************
 *
 * @file rwnx_msg_rx.c
 *
 * @brief RX function definitions
 *
 * Copyright (C) RivieraWaves 2012-2019
 *
 ****************************************************************************************
 */
#include "rwnx_defs.h"
#include "rwnx_prof.h"
#include "rwnx_tx.h"
#ifdef CONFIG_RWNX_BFMER
#include "rwnx_bfmer.h"
#endif //(CONFIG_RWNX_BFMER)
#ifdef CONFIG_RWNX_FULLMAC
#include "rwnx_debugfs.h"
#include "rwnx_msg_tx.h"
#include "rwnx_tdls.h"
#endif /* CONFIG_RWNX_FULLMAC */
#include "rwnx_events.h"
#include "rwnx_compat.h"
#include "aicwf_txrxif.h"
#include "rwnx_msg_rx.h"

static int rwnx_freq_to_idx(struct rwnx_hw *rwnx_hw, int freq)
{
	struct ieee80211_supported_band *sband = NULL;
	int band, ch, idx = 0;

	for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) {
#ifdef CONFIG_RWNX_FULLMAC
		sband = rwnx_hw->wiphy->bands[band];
#endif /* CONFIG_RWNX_FULLMAC */
		if (!sband) {
			continue;
		}

		for (ch = 0; ch < sband->n_channels; ch++, idx++) {
			if (sband->channels[ch].center_freq == freq) {
				goto exit;
			}
		}
	}

	BUG_ON(1);

exit:
	// Channel has been found, return the index
	return idx;
}

/***************************************************************************
 * Messages from MM task
 **************************************************************************/
static inline int rwnx_rx_chan_pre_switch_ind(struct rwnx_hw *rwnx_hw,
											  struct rwnx_cmd *cmd,
											  struct ipc_e2a_msg *msg)
{
	struct rwnx_vif *rwnx_vif;
	int chan_idx = ((struct mm_channel_pre_switch_ind *)msg->param)->chan_index;

	REG_SW_SET_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_PSWTCH_BIT);

#ifdef CONFIG_RWNX_FULLMAC
	list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
		if (rwnx_vif->up && rwnx_vif->ch_index == chan_idx) {
			rwnx_txq_vif_stop(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
		}
	}
#endif /* CONFIG_RWNX_FULLMAC */

	REG_SW_CLEAR_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_PSWTCH_BIT);

	return 0;
}

static inline int rwnx_rx_chan_switch_ind(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	struct rwnx_vif *rwnx_vif;
	int chan_idx = ((struct mm_channel_switch_ind *)msg->param)->chan_index;
	bool roc     = ((struct mm_channel_switch_ind *)msg->param)->roc;
	bool roc_tdls = ((struct mm_channel_switch_ind *)msg->param)->roc_tdls;

	REG_SW_SET_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_SWTCH_BIT);

#ifdef CONFIG_RWNX_FULLMAC
	if (roc_tdls) {
		u8 vif_index = ((struct mm_channel_switch_ind *)msg->param)->vif_index;
		list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
			if (rwnx_vif->vif_index == vif_index) {
				rwnx_vif->roc_tdls = true;
				rwnx_txq_tdls_sta_start(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
			}
		}
	} else if (!roc) {
		list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
			if (rwnx_vif->up && rwnx_vif->ch_index == chan_idx) {
				rwnx_txq_vif_start(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
			}
		}
	} else {
		/* Retrieve the allocated RoC element */
		struct rwnx_roc_elem *roc_elem = rwnx_hw->roc_elem;
		/* Get VIF on which RoC has been started */
		//rwnx_vif = netdev_priv(roc_elem->wdev->netdev);

		/* For debug purpose (use ftrace kernel option) */
		//trace_switch_roc(rwnx_vif->vif_index);

		if(roc_elem) {
			/* If mgmt_roc is true, remain on channel has been started by ourself */
			if (!roc_elem->mgmt_roc) {
				/* Inform the host that we have switch on the indicated off-channel */
				cfg80211_ready_on_channel(roc_elem->wdev, (u64)(rwnx_hw->roc_cookie_cnt),
									  roc_elem->chan, roc_elem->duration, GFP_ATOMIC);
			}

			/* Keep in mind that we have switched on the channel */
			roc_elem->on_chan = true;
		} else {
			printk("roc_elem == null\n");
		}
		// Enable traffic on OFF channel queue
		rwnx_txq_offchan_start(rwnx_hw);
	}

	tasklet_schedule(&rwnx_hw->task);

	rwnx_hw->cur_chanctx = chan_idx;
	rwnx_radar_detection_enable_on_cur_channel(rwnx_hw);

#endif /* CONFIG_RWNX_FULLMAC */

	REG_SW_CLEAR_PROFILING_CHAN(rwnx_hw, SW_PROF_CHAN_CTXT_SWTCH_BIT);

	return 0;
}

static inline int rwnx_rx_tdls_chan_switch_cfm(struct rwnx_hw *rwnx_hw,
												struct rwnx_cmd *cmd,
												struct ipc_e2a_msg *msg)
{
	return 0;
}

static inline int rwnx_rx_tdls_chan_switch_ind(struct rwnx_hw *rwnx_hw,
											   struct rwnx_cmd *cmd,
											   struct ipc_e2a_msg *msg)
{
#ifdef CONFIG_RWNX_FULLMAC
	// Enable traffic on OFF channel queue
	rwnx_txq_offchan_start(rwnx_hw);

	return 0;
#endif
}

static inline int rwnx_rx_tdls_chan_switch_base_ind(struct rwnx_hw *rwnx_hw,
													struct rwnx_cmd *cmd,
													struct ipc_e2a_msg *msg)
{
	struct rwnx_vif *rwnx_vif;
	u8 vif_index = ((struct tdls_chan_switch_base_ind *)msg->param)->vif_index;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

#ifdef CONFIG_RWNX_FULLMAC
	list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
		if (rwnx_vif->vif_index == vif_index) {
			rwnx_vif->roc_tdls = false;
			rwnx_txq_tdls_sta_stop(rwnx_vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
		}
	}
	return 0;
#endif
}

static inline int rwnx_rx_tdls_peer_ps_ind(struct rwnx_hw *rwnx_hw,
										   struct rwnx_cmd *cmd,
										   struct ipc_e2a_msg *msg)
{
	struct rwnx_vif *rwnx_vif;
	u8 vif_index = ((struct tdls_peer_ps_ind *)msg->param)->vif_index;
	bool ps_on = ((struct tdls_peer_ps_ind *)msg->param)->ps_on;

#ifdef CONFIG_RWNX_FULLMAC
list_for_each_entry(rwnx_vif, &rwnx_hw->vifs, list) {
	if (rwnx_vif->vif_index == vif_index) {
		rwnx_vif->sta.tdls_sta->tdls.ps_on = ps_on;
		// Update PS status for the TDLS station
		rwnx_ps_bh_enable(rwnx_hw, rwnx_vif->sta.tdls_sta, ps_on);
		}
	}

	return 0;
#endif
}

static inline int rwnx_rx_remain_on_channel_exp_ind(struct rwnx_hw *rwnx_hw,
													struct rwnx_cmd *cmd,
													struct ipc_e2a_msg *msg)
{
#ifdef CONFIG_RWNX_FULLMAC
	/* Retrieve the allocated RoC element */
	struct rwnx_roc_elem *roc_elem = rwnx_hw->roc_elem;
	/* Get VIF on which RoC has been started */
	struct rwnx_vif *rwnx_vif;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	if (!roc_elem)
		return 0;

	rwnx_vif = container_of(roc_elem->wdev, struct rwnx_vif, wdev);
	/* For debug purpose (use ftrace kernel option) */
#ifdef CREATE_TRACE_POINTS
	trace_roc_exp(rwnx_vif->vif_index);
#endif
	/* If mgmt_roc is true, remain on channel has been started by ourself */
	/* If RoC has been cancelled before we switched on channel, do not call cfg80211 */
	if (!roc_elem->mgmt_roc && roc_elem->on_chan) {
		/* Inform the host that off-channel period has expired */
		cfg80211_remain_on_channel_expired(roc_elem->wdev, (u64)(rwnx_hw->roc_cookie_cnt),
										   roc_elem->chan, GFP_ATOMIC);
	}

	/* De-init offchannel TX queue */
	rwnx_txq_offchan_deinit(rwnx_vif);

	/* Increase the cookie counter cannot be zero */
	rwnx_hw->roc_cookie_cnt++;

	if (rwnx_hw->roc_cookie_cnt == 0) {
		rwnx_hw->roc_cookie_cnt = 1;
	}

	/* Free the allocated RoC element */
	kfree(roc_elem);
	rwnx_hw->roc_elem = NULL;

#endif /* CONFIG_RWNX_FULLMAC */
	return 0;
}

static inline int rwnx_rx_p2p_vif_ps_change_ind(struct rwnx_hw *rwnx_hw,
												struct rwnx_cmd *cmd,
												struct ipc_e2a_msg *msg)
{
	int vif_idx  = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->vif_index;
	int ps_state = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->ps_state;
	struct rwnx_vif *vif_entry;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

#ifdef CONFIG_RWNX_FULLMAC
	vif_entry = rwnx_hw->vif_table[vif_idx];

	if (vif_entry) {
		goto found_vif;
	}
#endif /* CONFIG_RWNX_FULLMAC */

	goto exit;

found_vif:

#ifdef CONFIG_RWNX_FULLMAC
	if (ps_state == MM_PS_MODE_OFF) {
		// Start TX queues for provided VIF
		rwnx_txq_vif_start(vif_entry, RWNX_TXQ_STOP_VIF_PS, rwnx_hw);
	} else {
		// Stop TX queues for provided VIF
		rwnx_txq_vif_stop(vif_entry, RWNX_TXQ_STOP_VIF_PS, rwnx_hw);
	}
#endif /* CONFIG_RWNX_FULLMAC */

exit:
	return 0;
}

static inline int rwnx_rx_channel_survey_ind(struct rwnx_hw *rwnx_hw,
											 struct rwnx_cmd *cmd,
											 struct ipc_e2a_msg *msg)
{
	struct mm_channel_survey_ind *ind = (struct mm_channel_survey_ind *)msg->param;
	// Get the channel index
	int idx = rwnx_freq_to_idx(rwnx_hw, ind->freq);
	// Get the survey
	struct rwnx_survey_info *rwnx_survey;

	if (idx >  ARRAY_SIZE(rwnx_hw->survey))
		return 0;

	rwnx_survey = &rwnx_hw->survey[idx];

	// Store the received parameters
	rwnx_survey->chan_time_ms = ind->chan_time_ms;
	rwnx_survey->chan_time_busy_ms = ind->chan_time_busy_ms;
	rwnx_survey->noise_dbm = ind->noise_dbm;
	rwnx_survey->filled = (SURVEY_INFO_TIME |
						   SURVEY_INFO_TIME_BUSY);

	if (ind->noise_dbm != 0) {
		rwnx_survey->filled |= SURVEY_INFO_NOISE_DBM;
	}

	return 0;
}

static inline int rwnx_rx_p2p_noa_upd_ind(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	return 0;
}

static inline int rwnx_rx_rssi_status_ind(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	struct mm_rssi_status_ind *ind = (struct mm_rssi_status_ind *)msg->param;
	int vif_idx  = ind->vif_index;
	bool rssi_status = ind->rssi_status;

	struct rwnx_vif *vif_entry;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

#ifdef CONFIG_RWNX_FULLMAC
vif_entry = rwnx_hw->vif_table[vif_idx];
	if (vif_entry) {
		cfg80211_cqm_rssi_notify(vif_entry->ndev,
			rssi_status ? NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW :
			NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH,
			ind->rssi, GFP_ATOMIC);
	}
#endif /* CONFIG_RWNX_FULLMAC */

	return 0;
}

static inline int rwnx_rx_pktloss_notify_ind(struct rwnx_hw *rwnx_hw,
											 struct rwnx_cmd *cmd,
											 struct ipc_e2a_msg *msg)
{
#ifdef CONFIG_RWNX_FULLMAC
	struct mm_pktloss_ind *ind = (struct mm_pktloss_ind *)msg->param;
	struct rwnx_vif *vif_entry;
	int vif_idx  = ind->vif_index;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	vif_entry = rwnx_hw->vif_table[vif_idx];
	if (vif_entry) {
		cfg80211_cqm_pktloss_notify(vif_entry->ndev, (const u8 *)ind->mac_addr.array,
									ind->num_packets, GFP_ATOMIC);
	}
#endif /* CONFIG_RWNX_FULLMAC */

	return 0;
}

static inline int rwnx_apm_staloss_ind(struct rwnx_hw *rwnx_hw,
                                                struct rwnx_cmd *cmd,
                                                struct ipc_e2a_msg *msg)
{
    struct mm_apm_staloss_ind *ind = (struct mm_apm_staloss_ind *)msg->param;

    RWNX_DBG(RWNX_FN_ENTRY_STR);

    memcpy(rwnx_hw->sta_mac_addr, ind->mac_addr, 6);
    rwnx_hw->apm_vif_idx = ind->vif_idx;

    queue_work(rwnx_hw->apmStaloss_wq, &rwnx_hw->apmStalossWork);

    return 0;
}

static inline int rwnx_rx_csa_counter_ind(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	struct mm_csa_counter_ind *ind = (struct mm_csa_counter_ind *)msg->param;
	struct rwnx_vif *vif;
	bool found = false;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	// Look for VIF entry
	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
		if (vif->vif_index == ind->vif_index) {
			found = true;
			break;
		}
	}

	if (found) {
#ifdef CONFIG_RWNX_FULLMAC
	if (vif->ap.csa)
		vif->ap.csa->count = ind->csa_count;
	else
		netdev_err(vif->ndev, "CSA counter update but no active CSA");

#endif
	}

	return 0;
}

#ifdef CONFIG_RWNX_FULLMAC
static inline int rwnx_rx_csa_finish_ind(struct rwnx_hw *rwnx_hw,
										 struct rwnx_cmd *cmd,
										 struct ipc_e2a_msg *msg)
{
	struct mm_csa_finish_ind *ind = (struct mm_csa_finish_ind *)msg->param;
	struct rwnx_vif *vif;
	bool found = false;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	// Look for VIF entry
	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
		if (vif->vif_index == ind->vif_index) {
			found = true;
			break;
		}
	}

	if (found) {
		if (RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP ||
			RWNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO) {
			if (vif->ap.csa) {
				vif->ap.csa->status = ind->status;
				vif->ap.csa->ch_idx = ind->chan_idx;
				schedule_work(&vif->ap.csa->work);
			} else
				netdev_err(vif->ndev, "CSA finish indication but no active CSA");
		} else {
			if (ind->status == 0) {
				rwnx_chanctx_unlink(vif);
				rwnx_chanctx_link(vif, ind->chan_idx, NULL);
				if (rwnx_hw->cur_chanctx == ind->chan_idx) {
					rwnx_radar_detection_enable_on_cur_channel(rwnx_hw);
					rwnx_txq_vif_start(vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
				} else
					rwnx_txq_vif_stop(vif, RWNX_TXQ_STOP_CHAN, rwnx_hw);
			}
		}
	}

	return 0;
}

static inline int rwnx_rx_csa_traffic_ind(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	struct mm_csa_traffic_ind *ind = (struct mm_csa_traffic_ind *)msg->param;
	struct rwnx_vif *vif;
	bool found = false;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	// Look for VIF entry
	list_for_each_entry(vif, &rwnx_hw->vifs, list) {
		if (vif->vif_index == ind->vif_index) {
			found = true;
			break;
		}
	}

	if (found) {
		if (ind->enable)
			rwnx_txq_vif_start(vif, RWNX_TXQ_STOP_CSA, rwnx_hw);
		else
			rwnx_txq_vif_stop(vif, RWNX_TXQ_STOP_CSA, rwnx_hw);
	}

	return 0;
}

static inline int rwnx_rx_ps_change_ind(struct rwnx_hw *rwnx_hw,
										struct rwnx_cmd *cmd,
										struct ipc_e2a_msg *msg)
{
	struct mm_ps_change_ind *ind = (struct mm_ps_change_ind *)msg->param;
	struct rwnx_sta *sta = &rwnx_hw->sta_table[ind->sta_idx];

	if (ind->sta_idx >= (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) {
		wiphy_err(rwnx_hw->wiphy, "Invalid sta index reported by fw %d\n",
				  ind->sta_idx);
		return 1;
	}

	netdev_dbg(rwnx_hw->vif_table[sta->vif_idx]->ndev,
			   "Sta %d, change PS mode to %s", sta->sta_idx,
			   ind->ps_state ? "ON" : "OFF");

	if (sta->valid) {
		rwnx_ps_bh_enable(rwnx_hw, sta, ind->ps_state);
	} else if (rwnx_hw->adding_sta) {
		sta->ps.active = ind->ps_state ? true : false;
	} else {
		if (rwnx_hw->vif_table[sta->vif_idx]->ndev)
			netdev_err(rwnx_hw->vif_table[sta->vif_idx]->ndev,
				   "Ignore PS mode change on invalid sta\n");
	}

	return 0;
}


static inline int rwnx_rx_traffic_req_ind(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	struct mm_traffic_req_ind *ind = (struct mm_traffic_req_ind *)msg->param;
	struct rwnx_sta *sta = &rwnx_hw->sta_table[ind->sta_idx];

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	netdev_dbg(rwnx_hw->vif_table[sta->vif_idx]->ndev,
			   "Sta %d, asked for %d pkt", sta->sta_idx, ind->pkt_cnt);

	rwnx_ps_bh_traffic_req(rwnx_hw, sta, ind->pkt_cnt,
						   ind->uapsd ? UAPSD_ID : LEGACY_PS_ID);

	return 0;
}
#endif /* CONFIG_RWNX_FULLMAC */

/***************************************************************************
 * Messages from SCAN task
 **************************************************************************/
#if 0
static inline int rwnx_rx_scan_done_ind(struct rwnx_hw *rwnx_hw,
										struct rwnx_cmd *cmd,
										struct ipc_e2a_msg *msg)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
	struct cfg80211_scan_info info = {
		.aborted = false,
	};
#endif
	RWNX_DBG(RWNX_FN_ENTRY_STR);

	rwnx_ipc_elem_var_deallocs(rwnx_hw, &rwnx_hw->scan_ie);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
	ieee80211_scan_completed(rwnx_hw->hw, &info);
#else
	ieee80211_scan_completed(rwnx_hw->hw, false);
#endif

	return 0;
}
#endif

/***************************************************************************
 * Messages from SCANU task
 **************************************************************************/
#ifdef CONFIG_RWNX_FULLMAC
extern uint8_t scanning;
static inline int rwnx_rx_scanu_start_cfm(struct rwnx_hw *rwnx_hw,
										  struct rwnx_cmd *cmd,
										  struct ipc_e2a_msg *msg)
{
	RWNX_DBG(RWNX_FN_ENTRY_STR);

	scanning = 0;
	if (rwnx_hw->scan_request) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
		struct cfg80211_scan_info info = {
			.aborted = false,
		};

		cfg80211_scan_done(rwnx_hw->scan_request, &info);
#else
		cfg80211_scan_done(rwnx_hw->scan_request, false);
#endif
	}

	rwnx_hw->scan_request = NULL;

	return 0;
}

static inline int rwnx_rx_scanu_result_ind(struct rwnx_hw *rwnx_hw,
										   struct rwnx_cmd *cmd,
										   struct ipc_e2a_msg *msg)
{
	struct cfg80211_bss *bss = NULL;
	struct ieee80211_channel *chan;
	struct scanu_result_ind *ind = (struct scanu_result_ind *)msg->param;
	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)ind->payload;
	u64 tsf;
	u8 *ie;
	size_t ielen;
	u16 capability, beacon_interval;
	u16 len = ind->length;

	chan = ieee80211_get_channel(rwnx_hw->wiphy, ind->center_freq);

	if (chan != NULL) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
        struct timespec ts;
		get_monotonic_boottime(&ts);
		tsf = (u64)ts.tv_sec * 1000000 + div_u64(ts.tv_nsec, 1000);
        mgmt->u.probe_resp.timestamp = ((u64)ts.tv_sec*1000000) + ts.tv_nsec/1000;
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
		struct timespec ts;
		ts = ktime_to_timespec(ktime_get_boottime());
		tsf = (u64)ts.tv_sec * 1000000 + div_u64(ts.tv_nsec, 1000);
		mgmt->u.probe_resp.timestamp = tsf;
#else
		struct timespec64 ts;
		ts = ktime_to_timespec64(ktime_get_boottime());
		tsf = (u64)ts.tv_sec * 1000000 + div_u64(ts.tv_nsec, 1000);
		mgmt->u.probe_resp.timestamp = tsf;
#endif
	ie = mgmt->u.probe_resp.variable;
	ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
	beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int);
	capability = le16_to_cpu(mgmt->u.probe_resp.capab_info);
	/* framework use system bootup time */
	bss = cfg80211_inform_bss(rwnx_hw->wiphy, chan,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0))
				CFG80211_BSS_FTYPE_UNKNOWN,
#endif
				mgmt->bssid, tsf, capability, beacon_interval,
				ie, ielen, ind->rssi * 100, GFP_ATOMIC);
	}

	if (bss != NULL)
		cfg80211_put_bss(rwnx_hw->wiphy, bss);

	return 0;
}
#endif /* CONFIG_RWNX_FULLMAC */

/***************************************************************************
 * Messages from ME task
 **************************************************************************/
#ifdef CONFIG_RWNX_FULLMAC
static inline int rwnx_rx_me_tkip_mic_failure_ind(struct rwnx_hw *rwnx_hw,
												  struct rwnx_cmd *cmd,
												  struct ipc_e2a_msg *msg)
{
	struct me_tkip_mic_failure_ind *ind = (struct me_tkip_mic_failure_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
	struct net_device *dev = rwnx_vif->ndev;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	cfg80211_michael_mic_failure(dev, (u8 *)&ind->addr, (ind->ga ? NL80211_KEYTYPE_GROUP :
								 NL80211_KEYTYPE_PAIRWISE), ind->keyid,
								 (u8 *)&ind->tsc, GFP_ATOMIC);

	return 0;
}

static inline int rwnx_rx_me_tx_credits_update_ind(struct rwnx_hw *rwnx_hw,
												   struct rwnx_cmd *cmd,
												   struct ipc_e2a_msg *msg)
{
	struct me_tx_credits_update_ind *ind = (struct me_tx_credits_update_ind *)msg->param;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	rwnx_txq_credit_update(rwnx_hw, ind->sta_idx, ind->tid, ind->credits);

	return 0;
}
#endif /* CONFIG_RWNX_FULLMAC */

/***************************************************************************
 * Messages from SM task
 **************************************************************************/
#ifdef CONFIG_RWNX_FULLMAC
static inline int rwnx_rx_sm_connect_ind(struct rwnx_hw *rwnx_hw,
										 struct rwnx_cmd *cmd,
										 struct ipc_e2a_msg *msg)
{
	struct sm_connect_ind *ind = (struct sm_connect_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
	struct net_device *dev = rwnx_vif->ndev;
	const u8 *req_ie, *rsp_ie;
	const u8 *extcap_ie;
	const struct ieee_types_extcap *extcap;
	struct ieee80211_channel *chan;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	/* Retrieve IE addresses and lengths */
	req_ie = (const u8 *)ind->assoc_ie_buf;
	rsp_ie = req_ie + ind->assoc_req_ie_len;

    // Fill-in the AP information
	AICWFDBG(LOGINFO, "%s ind->status_code:%d \r\n", __func__, ind->status_code);

	if (ind->status_code == 0) {
		struct rwnx_sta *sta = &rwnx_hw->sta_table[ind->ap_idx];
		u8 txq_status;
		struct cfg80211_chan_def chandef;

		sta->valid = true;
		sta->sta_idx = ind->ap_idx;
		sta->ch_idx = ind->ch_idx;
		sta->vif_idx = ind->vif_idx;
		sta->vlan_idx = sta->vif_idx;
		sta->qos = ind->qos;
		sta->acm = ind->acm;
		sta->ps.active = false;
		sta->aid = ind->aid;
		sta->band = ind->band;
		sta->width = ind->width;
		sta->center_freq = ind->center_freq;
		sta->center_freq1 = ind->center_freq1;
		sta->center_freq2 = ind->center_freq2;
		rwnx_vif->sta.ap = sta;
		chan = ieee80211_get_channel(rwnx_hw->wiphy, ind->center_freq);
		cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT);
		if (!rwnx_hw->mod_params->ht_on)
			chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
		else
			chandef.width = chnl2bw[ind->width];
		chandef.center_freq1 = ind->center_freq1;
		chandef.center_freq2 = ind->center_freq2;
		rwnx_chanctx_link(rwnx_vif, ind->ch_idx, &chandef);
		memcpy(sta->mac_addr, ind->bssid.array, ETH_ALEN);
		if (ind->ch_idx == rwnx_hw->cur_chanctx) {
			txq_status = 0;
		} else {
			txq_status = RWNX_TXQ_STOP_CHAN;
		}
		memcpy(sta->ac_param, ind->ac_param, sizeof(sta->ac_param));
		rwnx_txq_sta_init(rwnx_hw, sta, txq_status);
		rwnx_txq_tdls_vif_init(rwnx_vif);
#ifdef CONFIG_DEBUG_FS
		rwnx_dbgfs_register_rc_stat(rwnx_hw, sta);
#endif
		rwnx_mu_group_sta_init(sta, NULL);
		/* Look for TDLS Channel Switch Prohibited flag in the Extended Capability
		 * Information Element*/
		extcap_ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, rsp_ie, ind->assoc_rsp_ie_len);
		if (extcap_ie && extcap_ie[1] >= 5) {
			extcap = (void *)(extcap_ie);
			rwnx_vif->tdls_chsw_prohibited = extcap->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED;
		}

		if (rwnx_vif->wep_enabled)
			rwnx_vif->wep_auth_err = false;

#ifdef CONFIG_RWNX_BFMER
		/* If Beamformer feature is activated, check if features can be used
		 * with the new peer device
		 */
		if (rwnx_hw->mod_params->bfmer) {
			const u8 *vht_capa_ie;
			const struct ieee80211_vht_cap *vht_cap;

			do {
				/* Look for VHT Capability Information Element */
				vht_capa_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, rsp_ie,
											   ind->assoc_rsp_ie_len);

				/* Stop here if peer device does not support VHT */
				if (!vht_capa_ie) {
					break;
				}

				vht_cap = (const struct ieee80211_vht_cap *)(vht_capa_ie + 2);

				/* Send MM_BFMER_ENABLE_REQ message if needed */
				rwnx_send_bfmer_enable(rwnx_hw, sta, vht_cap);
			} while (0);
		}
#endif //(CONFIG_RWNX_BFMER)

#ifdef CONFIG_RWNX_MON_DATA
		// If there are 1 sta and 1 monitor interface active at the same time then
		// monitor interface channel context is always the same as the STA interface.
		// This doesn't work with 2 STA interfaces but we don't want to support it.
		if (rwnx_hw->monitor_vif != RWNX_INVALID_VIF) {
			struct rwnx_vif *rwnx_mon_vif = rwnx_hw->vif_table[rwnx_hw->monitor_vif];
			rwnx_chanctx_unlink(rwnx_mon_vif);
			rwnx_chanctx_link(rwnx_mon_vif, ind->ch_idx, NULL);
		}
#endif
		atomic_set(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_CONNECTED);
	} else if (ind->status_code == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG) {
		if (rwnx_vif->wep_enabled) {
			rwnx_vif->wep_auth_err = true;
			printk("con ind wep_auth_err %d\n", rwnx_vif->wep_auth_err);
		}
		atomic_set(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_DISCONNECTED);
	}else{
		atomic_set(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_DISCONNECTED);
	}

	if (!ind->roamed)
		cfg80211_connect_result(dev, (const u8 *)ind->bssid.array, req_ie,
								ind->assoc_req_ie_len, rsp_ie,
								ind->assoc_rsp_ie_len, ind->status_code,
								GFP_ATOMIC);
	else {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
		struct cfg80211_roam_info info;
		memset(&info, 0, sizeof(info));
		if (rwnx_vif->ch_index < NX_CHAN_CTXT_CNT)
			info.channel = rwnx_hw->chanctx_table[rwnx_vif->ch_index].chan_def.chan;
		info.bssid = (const u8 *)ind->bssid.array;
		info.req_ie = req_ie;
		info.req_ie_len = ind->assoc_req_ie_len;
		info.resp_ie = rsp_ie;
		info.resp_ie_len = ind->assoc_rsp_ie_len;
		cfg80211_roamed(dev, &info, GFP_ATOMIC);
#else
		chan = ieee80211_get_channel(rwnx_hw->wiphy, ind->center_freq);
		cfg80211_roamed(dev
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39) || defined(COMPAT_KERNEL_RELEASE)
			, chan
#endif
			, (const u8 *)ind->bssid.array
			, req_ie
			, ind->assoc_req_ie_len
			, rsp_ie
			, ind->assoc_rsp_ie_len
			, GFP_ATOMIC);
#endif /*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)*/
	}

	netif_tx_start_all_queues(dev);
	netif_carrier_on(dev);

	return 0;
}

extern u8 dhcped;
static inline int rwnx_rx_sm_disconnect_ind(struct rwnx_hw *rwnx_hw,
											struct rwnx_cmd *cmd,
											struct ipc_e2a_msg *msg)
{
	struct sm_disconnect_ind *ind = (struct sm_disconnect_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
	struct net_device *dev;
#ifdef AICWF_RX_REORDER
	struct reord_ctrl_info *reord_info, *tmp;
	u8 *macaddr;
	struct aicwf_rx_priv *rx_priv;
#endif

	RWNX_DBG(RWNX_FN_ENTRY_STR);
	dhcped = 0;

	if(!rwnx_vif)
		return 0;
	dev = rwnx_vif->ndev;

	if (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
		rwnx_hw->is_p2p_connected = 0;
	/* if vif is not up, rwnx_close has already been called */
	if (rwnx_vif->up) {
		if (!ind->ft_over_ds && !ind->reassoc) {
			cfg80211_disconnected(dev, ind->reason_code, NULL, 0,
								  (ind->reason_code <= 1), GFP_ATOMIC);
		}
		netif_tx_stop_all_queues(dev);
		netif_carrier_off(dev);
	}

#ifdef CONFIG_RWNX_BFMER
	/* Disable Beamformer if supported */
	rwnx_bfmer_report_del(rwnx_hw, rwnx_vif->sta.ap);
#endif //(CONFIG_RWNX_BFMER)

#ifdef AICWF_RX_REORDER
#ifdef AICWF_SDIO_SUPPORT
	rx_priv = rwnx_hw->sdiodev->rx_priv;
#else
	rx_priv = rwnx_hw->usbdev->rx_priv;
#endif
	if ((rwnx_vif->wdev.iftype == NL80211_IFTYPE_STATION) || (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)) {
		macaddr = rwnx_vif->ndev->dev_addr;
		printk("deinit:macaddr:%x,%x,%x,%x,%x,%x\r\n", macaddr[0], macaddr[1], macaddr[2], \
							   macaddr[3], macaddr[4], macaddr[5]);

		spin_lock_bh(&rx_priv->stas_reord_lock);
		list_for_each_entry_safe(reord_info, tmp, &rx_priv->stas_reord_list, list) {
			macaddr = rwnx_vif->ndev->dev_addr;
			printk("reord_mac:%x,%x,%x,%x,%x,%x\r\n", reord_info->mac_addr[0], reord_info->mac_addr[1], reord_info->mac_addr[2], \
								   reord_info->mac_addr[3], reord_info->mac_addr[4], reord_info->mac_addr[5]);
			if (!memcmp(reord_info->mac_addr, macaddr, 6)) {
				reord_deinit_sta(rx_priv, reord_info);
				break;
			}
		}
		spin_unlock_bh(&rx_priv->stas_reord_lock);
	} else if ((rwnx_vif->wdev.iftype == NL80211_IFTYPE_AP) || (rwnx_vif->wdev.iftype == NL80211_IFTYPE_P2P_GO)) {
		BUG();//should be not here: del_sta function
	}
#endif

	rwnx_txq_sta_deinit(rwnx_hw, rwnx_vif->sta.ap);
	rwnx_txq_tdls_vif_deinit(rwnx_vif);
	#if 0
	rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_vif->sta.ap);
	#endif
	rwnx_vif->sta.ap->valid = false;
	rwnx_vif->sta.ap = NULL;
	rwnx_external_auth_disable(rwnx_vif);
	rwnx_chanctx_unlink(rwnx_vif);
	
	msleep(200);
	atomic_set(&rwnx_vif->drv_conn_state, (int)RWNX_DRV_STATUS_DISCONNECTED);
	return 0;
}

static inline int rwnx_rx_sm_external_auth_required_ind(struct rwnx_hw *rwnx_hw,
														struct rwnx_cmd *cmd,
														struct ipc_e2a_msg *msg)
{
	struct sm_external_auth_required_ind *ind =
		(struct sm_external_auth_required_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) || defined(CONFIG_WPA3_FOR_OLD_KERNEL)
	struct net_device *dev = rwnx_vif->ndev;
	struct cfg80211_external_auth_params params;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	params.action = NL80211_EXTERNAL_AUTH_START;
	memcpy(params.bssid, ind->bssid.array, ETH_ALEN);
	params.ssid.ssid_len = ind->ssid.length;
	memcpy(params.ssid.ssid, ind->ssid.array,
		   min_t(size_t, ind->ssid.length, sizeof(params.ssid.ssid)));
	params.key_mgmt_suite = ind->akm;

	if ((ind->vif_idx > NX_VIRT_DEV_MAX) || !rwnx_vif->up ||
		(RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_STATION) ||
		cfg80211_external_auth_request(dev, &params, GFP_ATOMIC)) {
		wiphy_err(rwnx_hw->wiphy, "Failed to start external auth on vif %d",
				  ind->vif_idx);
		rwnx_send_sm_external_auth_required_rsp(rwnx_hw, rwnx_vif,
												WLAN_STATUS_UNSPECIFIED_FAILURE);
		return 0;
	}

	rwnx_external_auth_enable(rwnx_vif);
#else
	rwnx_send_sm_external_auth_required_rsp(rwnx_hw, rwnx_vif,
											WLAN_STATUS_UNSPECIFIED_FAILURE);
#endif
	return 0;
}


static inline int rwnx_rx_mesh_path_create_cfm(struct rwnx_hw *rwnx_hw,
											   struct rwnx_cmd *cmd,
											   struct ipc_e2a_msg *msg)
{
	struct mesh_path_create_cfm *cfm = (struct mesh_path_create_cfm *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[cfm->vif_idx];

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	/* Check we well have a Mesh Point Interface */
	if (rwnx_vif && (RWNX_VIF_TYPE(rwnx_vif) == NL80211_IFTYPE_MESH_POINT)) {
		rwnx_vif->ap.create_path = false;
	}

	return 0;
}

static inline int rwnx_rx_mesh_peer_update_ind(struct rwnx_hw *rwnx_hw,
											   struct rwnx_cmd *cmd,
											   struct ipc_e2a_msg *msg)
{
	struct mesh_peer_update_ind *ind = (struct mesh_peer_update_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
	struct rwnx_sta *rwnx_sta = &rwnx_hw->sta_table[ind->sta_idx];

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	if ((ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX)) ||
		(rwnx_vif && (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT)) ||
		(ind->sta_idx >= NX_REMOTE_STA_MAX))
		return 1;

	/* Check we well have a Mesh Point Interface */
	if (!rwnx_vif->user_mpm) {
		/* Check if peer link has been established or lost */
		if (ind->estab) {
			if (!rwnx_sta->valid) {
				u8 txq_status;

				rwnx_sta->valid = true;
				rwnx_sta->sta_idx = ind->sta_idx;
				rwnx_sta->ch_idx = rwnx_vif->ch_index;
				rwnx_sta->vif_idx = ind->vif_idx;
				rwnx_sta->vlan_idx = rwnx_sta->vif_idx;
				rwnx_sta->ps.active = false;
				rwnx_sta->qos = true;
				rwnx_sta->aid = ind->sta_idx + 1;
				//rwnx_sta->acm = ind->acm;
				memcpy(rwnx_sta->mac_addr, ind->peer_addr.array, ETH_ALEN);

				rwnx_chanctx_link(rwnx_vif, rwnx_sta->ch_idx, NULL);

				/* Add the station in the list of VIF's stations */
				INIT_LIST_HEAD(&rwnx_sta->list);
				list_add_tail(&rwnx_sta->list, &rwnx_vif->ap.sta_list);

				/* Initialize the TX queues */
				if (rwnx_sta->ch_idx == rwnx_hw->cur_chanctx) {
					txq_status = 0;
				} else {
					txq_status = RWNX_TXQ_STOP_CHAN;
				}

				rwnx_txq_sta_init(rwnx_hw, rwnx_sta, txq_status);
#ifdef CONFIG_DEBUG_FS
				rwnx_dbgfs_register_rc_stat(rwnx_hw, rwnx_sta);
#endif
#ifdef CONFIG_RWNX_BFMER
				// TODO: update indication to contains vht capabilties
				if (rwnx_hw->mod_params->bfmer)
					rwnx_send_bfmer_enable(rwnx_hw, rwnx_sta, NULL);

				rwnx_mu_group_sta_init(rwnx_sta, NULL);
#endif /* CONFIG_RWNX_BFMER */

			} else {
				WARN_ON(0);
			}
		} else {
			if (rwnx_sta->valid) {
				rwnx_sta->ps.active = false;
				rwnx_sta->valid = false;

				/* Remove the station from the list of VIF's station */
				list_del_init(&rwnx_sta->list);

				rwnx_txq_sta_deinit(rwnx_hw, rwnx_sta);
#ifdef CONFIG_DEBUG_FS
				rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_sta);
#endif
			} else {
				WARN_ON(0);
			}
		}
	} else {
		if (!ind->estab && rwnx_sta->valid) {
			/* There is no way to inform upper layer for lost of peer, still
			   clean everything in the driver */
			rwnx_sta->ps.active = false;
			rwnx_sta->valid = false;

			/* Remove the station from the list of VIF's station */
			list_del_init(&rwnx_sta->list);

			rwnx_txq_sta_deinit(rwnx_hw, rwnx_sta);
#ifdef CONFIG_DEBUG_FS
			rwnx_dbgfs_unregister_rc_stat(rwnx_hw, rwnx_sta);
#endif
		} else {
			WARN_ON(0);
		}
	}

	return 0;
}

static inline int rwnx_rx_mesh_path_update_ind(struct rwnx_hw *rwnx_hw,
											   struct rwnx_cmd *cmd,
											   struct ipc_e2a_msg *msg)
{
	struct mesh_path_update_ind *ind = (struct mesh_path_update_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
	struct rwnx_mesh_path *mesh_path;
	bool found = false;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX))
		return 1;

	if (!rwnx_vif || (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT))
		return 0;

	/* Look for path with provided target address */
	list_for_each_entry(mesh_path, &rwnx_vif->ap.mpath_list, list) {
		if (mesh_path->path_idx == ind->path_idx) {
			found = true;
			break;
		}
	}

	/* Check if element has been deleted */
	if (ind->delete) {
		if (found) {
#ifdef CREATE_TRACE_POINTS
			trace_mesh_delete_path(mesh_path);
#endif
			/* Remove element from list */
			list_del_init(&mesh_path->list);
			/* Free the element */
			kfree(mesh_path);
		}
	} else {
		if (found) {
			// Update the Next Hop STA
			mesh_path->p_nhop_sta = &rwnx_hw->sta_table[ind->nhop_sta_idx];
#ifdef CREATE_TRACE_POINTS
			trace_mesh_update_path(mesh_path);
#endif
		} else {
			// Allocate a Mesh Path structure
			mesh_path = (struct rwnx_mesh_path *)kmalloc(sizeof(struct rwnx_mesh_path), GFP_ATOMIC);

			if (mesh_path) {
				INIT_LIST_HEAD(&mesh_path->list);

				mesh_path->path_idx = ind->path_idx;
				mesh_path->p_nhop_sta = &rwnx_hw->sta_table[ind->nhop_sta_idx];
				memcpy(&mesh_path->tgt_mac_addr, &ind->tgt_mac_addr, MAC_ADDR_LEN);

				// Insert the path in the list of path
				list_add_tail(&mesh_path->list, &rwnx_vif->ap.mpath_list);
#ifdef CREATE_TRACE_POINTS
				trace_mesh_create_path(mesh_path);
#endif
			}
		}
	}

	return 0;
}

static inline int rwnx_rx_mesh_proxy_update_ind(struct rwnx_hw *rwnx_hw,
											   struct rwnx_cmd *cmd,
											   struct ipc_e2a_msg *msg)
{
	struct mesh_proxy_update_ind *ind = (struct mesh_proxy_update_ind *)msg->param;
	struct rwnx_vif *rwnx_vif = rwnx_hw->vif_table[ind->vif_idx];
	struct rwnx_mesh_proxy *mesh_proxy;
	bool found = false;

	RWNX_DBG(RWNX_FN_ENTRY_STR);

	if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX))
		return 1;

	if (!rwnx_vif || (RWNX_VIF_TYPE(rwnx_vif) != NL80211_IFTYPE_MESH_POINT))
		return 0;

	/* Look for path with provided external STA address */
	list_for_each_entry(mesh_proxy, &rwnx_vif->ap.proxy_list, list) {
		if (!memcmp(&ind->ext_sta_addr, &mesh_proxy->ext_sta_addr, ETH_ALEN)) {
			found = true;
			break;
		}
	}

	if (ind->delete && found) {
		/* Delete mesh path */
		list_del_init(&mesh_proxy->list);
		kfree(mesh_proxy);
	} else if (!ind->delete && !found) {
		/* Allocate a Mesh Path structure */
		mesh_proxy = (struct rwnx_mesh_proxy *)kmalloc(sizeof(*mesh_proxy),
													   GFP_ATOMIC);

		if (mesh_proxy) {
			INIT_LIST_HEAD(&mesh_proxy->list);

			memcpy(&mesh_proxy->ext_sta_addr, &ind->ext_sta_addr, MAC_ADDR_LEN);
			mesh_proxy->local = ind->local;

			if (!ind->local) {
				memcpy(&mesh_proxy->proxy_addr, &ind->proxy_mac_addr, MAC_ADDR_LEN);
			}

			/* Insert the path in the list of path */
			list_add_tail(&mesh_proxy->list, &rwnx_vif->ap.proxy_list);
		}
	}

	return 0;
}
#endif /* CONFIG_RWNX_FULLMAC */

/***************************************************************************
 * Messages from APM task
 **************************************************************************/


/***************************************************************************
 * Messages from DEBUG task
 **************************************************************************/
static inline int rwnx_rx_dbg_error_ind(struct rwnx_hw *rwnx_hw,
										struct rwnx_cmd *cmd,
										struct ipc_e2a_msg *msg)
{
	RWNX_DBG(RWNX_FN_ENTRY_STR);

	return 0;
}

#ifdef CONFIG_MCU_MESSAGE
static inline int rwnx_rx_dbg_custmsg_ind(struct rwnx_hw *rwnx_hw,
                                          struct rwnx_cmd *cmd,
                                          struct ipc_e2a_msg *msg)
{
    dbg_custom_msg_ind_t * ind;
    char str_msg[32 + 1];
    int str_len;
    RWNX_DBG(RWNX_FN_ENTRY_STR);

    ind = (dbg_custom_msg_ind_t *)msg->param;
    str_len = (ind->len < 32) ? ind->len : 32;
    memcpy(str_msg, (char *)ind->buf, str_len);
    str_msg[str_len] = '\0';
    printk("CustMsgInd: cmd=0x%x, len=%d, str=%s\r\n", ind->cmd, ind->len, str_msg);

    return 0;
}
#endif

#ifdef CONFIG_RWNX_FULLMAC

static msg_cb_fct mm_hdlrs[MSG_I(MM_MAX)] = {
	[MSG_I(MM_CHANNEL_SWITCH_IND)]     = rwnx_rx_chan_switch_ind,
	[MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = rwnx_rx_chan_pre_switch_ind,
	[MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = rwnx_rx_remain_on_channel_exp_ind,
	[MSG_I(MM_PS_CHANGE_IND)]          = rwnx_rx_ps_change_ind,
	[MSG_I(MM_TRAFFIC_REQ_IND)]        = rwnx_rx_traffic_req_ind,
	[MSG_I(MM_P2P_VIF_PS_CHANGE_IND)]  = rwnx_rx_p2p_vif_ps_change_ind,
	[MSG_I(MM_CSA_COUNTER_IND)]        = rwnx_rx_csa_counter_ind,
	[MSG_I(MM_CSA_FINISH_IND)]         = rwnx_rx_csa_finish_ind,
	[MSG_I(MM_CSA_TRAFFIC_IND)]        = rwnx_rx_csa_traffic_ind,
	[MSG_I(MM_CHANNEL_SURVEY_IND)]     = rwnx_rx_channel_survey_ind,
	[MSG_I(MM_P2P_NOA_UPD_IND)]        = rwnx_rx_p2p_noa_upd_ind,
	[MSG_I(MM_RSSI_STATUS_IND)]        = rwnx_rx_rssi_status_ind,
	[MSG_I(MM_PKTLOSS_IND)]            = rwnx_rx_pktloss_notify_ind,
    [MSG_I(MM_APM_STALOSS_IND)]        = rwnx_apm_staloss_ind,
};

static msg_cb_fct scan_hdlrs[MSG_I(SCANU_MAX)] = {
	[MSG_I(SCANU_START_CFM)]           = rwnx_rx_scanu_start_cfm,
	[MSG_I(SCANU_RESULT_IND)]          = rwnx_rx_scanu_result_ind,
};

static msg_cb_fct me_hdlrs[MSG_I(ME_MAX)] = {
	[MSG_I(ME_TKIP_MIC_FAILURE_IND)] = rwnx_rx_me_tkip_mic_failure_ind,
	[MSG_I(ME_TX_CREDITS_UPDATE_IND)] = rwnx_rx_me_tx_credits_update_ind,
};

static msg_cb_fct sm_hdlrs[MSG_I(SM_MAX)] = {
	[MSG_I(SM_CONNECT_IND)]    = rwnx_rx_sm_connect_ind,
	[MSG_I(SM_DISCONNECT_IND)] = rwnx_rx_sm_disconnect_ind,
	[MSG_I(SM_EXTERNAL_AUTH_REQUIRED_IND)] = rwnx_rx_sm_external_auth_required_ind,
};

static msg_cb_fct apm_hdlrs[MSG_I(APM_MAX)] = {
};

static msg_cb_fct mesh_hdlrs[MSG_I(MESH_MAX)] = {
	[MSG_I(MESH_PATH_CREATE_CFM)]  = rwnx_rx_mesh_path_create_cfm,
	[MSG_I(MESH_PEER_UPDATE_IND)]  = rwnx_rx_mesh_peer_update_ind,
	[MSG_I(MESH_PATH_UPDATE_IND)]  = rwnx_rx_mesh_path_update_ind,
	[MSG_I(MESH_PROXY_UPDATE_IND)] = rwnx_rx_mesh_proxy_update_ind,
};

#endif /* CONFIG_RWNX_FULLMAC */

static msg_cb_fct dbg_hdlrs[MSG_I(DBG_MAX)] = {
	[MSG_I(DBG_ERROR_IND)]                = rwnx_rx_dbg_error_ind,
#ifdef CONFIG_MCU_MESSAGE
	[MSG_I(DBG_CUSTOM_MSG_IND)]           = rwnx_rx_dbg_custmsg_ind,
#endif
};

static msg_cb_fct tdls_hdlrs[MSG_I(TDLS_MAX)] = {
	[MSG_I(TDLS_CHAN_SWITCH_CFM)] = rwnx_rx_tdls_chan_switch_cfm,
	[MSG_I(TDLS_CHAN_SWITCH_IND)] = rwnx_rx_tdls_chan_switch_ind,
	[MSG_I(TDLS_CHAN_SWITCH_BASE_IND)] = rwnx_rx_tdls_chan_switch_base_ind,
	[MSG_I(TDLS_PEER_PS_IND)] = rwnx_rx_tdls_peer_ps_ind,
};

static msg_cb_fct *msg_hdlrs[] = {
	[TASK_MM]    = mm_hdlrs,
	[TASK_DBG]   = dbg_hdlrs,
#ifdef CONFIG_RWNX_FULLMAC
	[TASK_TDLS]  = tdls_hdlrs,
	[TASK_SCANU] = scan_hdlrs,
	[TASK_ME]    = me_hdlrs,
	[TASK_SM]    = sm_hdlrs,
	[TASK_APM]   = apm_hdlrs,
	[TASK_MESH]  = mesh_hdlrs,
#endif /* CONFIG_RWNX_FULLMAC */
};

/**
 *
 */
void rwnx_rx_handle_msg(struct rwnx_hw *rwnx_hw, struct ipc_e2a_msg *msg)
{
	//printk("%s msg->id:0x%x \r\n", __func__, msg->id);

	rwnx_hw->cmd_mgr->msgind(rwnx_hw->cmd_mgr, msg,
							msg_hdlrs[MSG_T(msg->id)][MSG_I(msg->id)]);
}

void rwnx_rx_handle_print(struct rwnx_hw *rwnx_hw, u8 *msg, u32 len)
{
	u8 *data_end = NULL;
	(void)data_end;

	if (!rwnx_hw || !rwnx_hw->fwlog_en) {
		pr_err("FWLOG-OVFL: %s", msg);
		return;
	}

	printk("FWLOG: %s", msg);

#ifdef CONFIG_RWNX_DEBUGFS
	data_end = rwnx_hw->debugfs.fw_log.buf.dataend;

	if (!rwnx_hw->debugfs.fw_log.buf.data)
		return ;

	//printk("end=%lx, len=%d\n", (unsigned long)rwnx_hw->debugfs.fw_log.buf.end, len);

	spin_lock_bh(&rwnx_hw->debugfs.fw_log.lock);

	if (rwnx_hw->debugfs.fw_log.buf.end + len > data_end) {
		int rem = data_end - rwnx_hw->debugfs.fw_log.buf.end;
		memcpy(rwnx_hw->debugfs.fw_log.buf.end, msg, rem);
		memcpy(rwnx_hw->debugfs.fw_log.buf.data, &msg[rem], len - rem);
		rwnx_hw->debugfs.fw_log.buf.end = rwnx_hw->debugfs.fw_log.buf.data + (len - rem);
	} else {
		memcpy(rwnx_hw->debugfs.fw_log.buf.end, msg, len);
		rwnx_hw->debugfs.fw_log.buf.end += len;
	}

	rwnx_hw->debugfs.fw_log.buf.size += len;
	if (rwnx_hw->debugfs.fw_log.buf.size > FW_LOG_SIZE)
		rwnx_hw->debugfs.fw_log.buf.size = FW_LOG_SIZE;

	spin_unlock_bh(&rwnx_hw->debugfs.fw_log.lock);
#endif
}

